package org.springrain.javadoc4openapi;

import com.thoughtworks.qdox.model.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 泛型和Class处理工具类
 *
 * @author springrain
 * @version 0.0.1
 * @since 0.0.1
 */
public class GenericClassUtils {

    // 在注释中增加@ignore的属性,忽略,不生成文档.
    private static final String ignoreDocletTag = "ignore";
    // 在注释中增加@required的属性,忽略,不生成文档.
    private static final String requiredDocletTag = "required";


    // java基础类型的映射,类似javaBaseTypeMap.put("java.lang.Long","integer,int64"); 值是openapi的type,format
    private static Map<String, String> javaBaseTypeMap = new HashMap<>();
    //忽略的参数类型,方法遇到map类型的参数不处理,map无法确定key和value
    private static List<String> ignoreParameterJavaType = new ArrayList<>();

    static {

        //增加忽略的类型,方法遇到map类型的参数不处理,map无法确定key和value
        ignoreParameterJavaType.add("org.springframework.ui.Model");
        ignoreParameterJavaType.add("javax.servlet.ServletRequest");
        ignoreParameterJavaType.add("javax.servlet.http.HttpServletRequest");
        ignoreParameterJavaType.add("java.util.Map");


        //增加基本的类型对照
        javaBaseTypeMap.put("java.lang.String", "string,string");
        javaBaseTypeMap.put("java.util.Date", "string,date");
        javaBaseTypeMap.put("java.util.List", "array,array");
        javaBaseTypeMap.put("java.util.Set", "array,array");
        javaBaseTypeMap.put("java.util.Collection", "array,array");
        javaBaseTypeMap.put("boolean", "boolean,boolean");
        javaBaseTypeMap.put("java.lang.Boolean", "boolean,boolean");
        javaBaseTypeMap.put("int", "integer,int32");
        javaBaseTypeMap.put("java.lang.Integer", "integer,int32");
        javaBaseTypeMap.put("long", "integer,int64");
        javaBaseTypeMap.put("java.lang.Long", "integer,int64");
        javaBaseTypeMap.put("double", "number,double");
        javaBaseTypeMap.put("java.lang.Double", "number,double");
        javaBaseTypeMap.put("float", "number,float");
        javaBaseTypeMap.put("java.lang.Float", "number,float");
        //文件上传类型
        javaBaseTypeMap.put("org.springframework.web.multipart.MultipartFile", "string,binary");
        javaBaseTypeMap.put("org.springframework.web.multipart.MultipartHttpServletRequest", "string,binary");
        javaBaseTypeMap.put("org.springframework.web.multipart.commons.CommonsMultipartFile", "string,binary");
        javaBaseTypeMap.put("org.springframework.web.multipart.MultipartFile[]", "array,binary");
        javaBaseTypeMap.put("org.springframework.web.multipart.MultipartHttpServletRequest[]", "array,binary");
        javaBaseTypeMap.put("org.springframework.web.multipart.commons.CommonsMultipartFile[]", "array,binary");

    }

    /**
     * 是否是忽略的类型
     *
     * @param typeName 类型名称
     * @return 是否是忽略的参数
     */
    static boolean isIgnoreParameterJavaType(String typeName) {
        return ignoreParameterJavaType.contains(typeName);
    }

    /**
     * @param typeName 类型名称
     * @return 返回长度为2的字符串数组, 0是type, 1是format
     */
    static String[] getJavaBaseType(String typeName) {
        String typeValue = javaBaseTypeMap.get(typeName);
        if (StringUtils.isBlank(typeValue)) {
            return null;
        }
        return typeValue.split(",");
    }

    /**
     * 封装ref引用类型的参数,并封装到定义, paths-uri-get-parameters和paths-uri-get-responses-200-content-application/json-schema都使用这个方法.
     * 处理array和object,如果是array,需要判断泛型是基础类型还是Object对象,基础对象拼写item,Object对象使用$ref引用 componentsSchemasMap
     * 需要处理参数中有泛型声明,需要把泛型对象带入到对象分析中
     *
     * @param schemaRefObjectMap   需要处理的schemaRef引用对象的Map
     * @param componentsSchemasMap 添加对象声明
     */
    static void wrapSchemaRefObjectMap(JavaType parameterJavaType, Map<String, Object> schemaRefObjectMap, Map<String, Object> componentsSchemasMap) {

        // type 类型 只处理 object 和array, object
        String type = "object";
        String[] tf = getJavaBaseType(parameterJavaType.getFullyQualifiedName());
        if (tf != null) {
            type = tf[0];
        }

        //如果是数组,需要声明的泛型,例如 List<User> 取到 User 这个类型,在schemaRefObjectMap中items展开,User这个类型放到componentsSchemasMap中
        if ("array".equalsIgnoreCase(type)) {
            //获取泛型,例如 List<User> 取到 User 这个类型
            List<JavaType> actualTypeArguments = ((JavaParameterizedType) parameterJavaType).getActualTypeArguments();
            wrapArrayGeneric(schemaRefObjectMap, actualTypeArguments, new HashMap<>(), componentsSchemasMap);
        } else {//如果是object对象
            schemaRefObjectMap.put("$ref", "#/components/schemas/" + getRefPath(parameterJavaType));
            //增加类型定义
            addComponentsSchemasRef(parameterJavaType, componentsSchemasMap);
        }


    }

    /**
     * 增加对象类型定义,遍历所有的属性,递归调用
     *
     * @param javaType             qdox的java类
     * @param componentsSchemasMap 对象定义的Map
     */
    private static void addComponentsSchemasRef(JavaType javaType, Map<String, Object> componentsSchemasMap) {
        //java引用路径
        String refPath = getRefPath(javaType);
        Object obj = componentsSchemasMap.get(refPath);
        if (obj != null) {//已经存在定义了
            return;
        }

        //把类型转为JavaClass,需要qdox读取到源码.直接强转无法获取到源码类型,从Map中取值
        //JavaClass javaClass = (JavaClass) javaType;
        JavaClass javaClass = JavaDoc4OpenAPI.javaClassMap.get(javaType.getFullyQualifiedName());
        if (javaClass == null) {
            javaClass = (JavaClass) javaType;
        }
        //泛型的实际类型 例如 java.lang.String
        List<JavaType> javaTypeGenericType = ((JavaParameterizedType) javaType).getActualTypeArguments();
        //java代码声明的泛型 例如<T>
        List<JavaTypeVariable<JavaGenericDeclaration>> typeParameters = javaClass.getTypeParameters();

        //映射类型名称 和 javaType,例如 T=java.lang.String.做个KV,后面遍历泛型的时候,可以直接取到泛型T对应的实际对象.
        Map<String, JavaType> genericJavaTypeMap = new HashMap<>();

        //有些场景获取的泛型类型和声明并不一致,比如没有读取到源码,就无法获取源码中声明的<T> typeParameters长度就会比javaTypeGenericType小
        //@todo 暂时先不处理
        //for (int i = 0; i < javaTypeGenericType.size(); i++) {
        int len=javaTypeGenericType.size();
        if (len> typeParameters.size()){
            len= typeParameters.size();
        }
        for (int i = 0; i < len; i++) {
            JavaTypeVariable<JavaGenericDeclaration> jt = typeParameters.get(i);
            genericJavaTypeMap.put(jt.getName(), javaTypeGenericType.get(i));
        }


        //组件对象的Map
        Map<String, Object> componentsSchemasObjectMap = new HashMap<>();
        //设置到componentsSchemasMap
        componentsSchemasMap.put(refPath, componentsSchemasObjectMap);
        //类型为object
        componentsSchemasObjectMap.put("type", "object");

        //获取对象所有的属性
        List<JavaField> javaFields = new ArrayList<>();
        getJavaClassFields(javaClass, javaFields);
        if (CollectionUtils.isEmpty(javaFields)) {//没有属性
            return;
        }

        //实体对象的properties
        Map<String, Object> componentsSchemasObjectPropertiesMap = new HashMap<>();
        componentsSchemasObjectMap.put("properties", componentsSchemasObjectPropertiesMap);

        //不能为空的字段
        List<String> componentsSchemasObjectRequiredList = new ArrayList<>();

        //循环所有的java字段
        for (JavaField javaField : javaFields) {


            // 如果是忽略的字段
            if (isIgnoreJavaField(javaField)) {
                continue;
            }

            //字段名称
            String name = javaField.getName();

            //获取java属性的get方法,必须有set方法的才作为文档依据
            BeanProperty beanProperty = javaClass.getBeanProperty(name, true);
            if (beanProperty == null) {//如果属性不存在
                continue;
            }
            if (beanProperty.getMutator() == null) {//如果没有set方法
                continue;
            }


            //字段是否必填,不能为空
            boolean required = isRequiredJavaField(javaField);
            if (required) {
                componentsSchemasObjectRequiredList.add(name);
            }


            //字段类型
            JavaType jfJavaType = javaField.getType();

            //字段类型名称
            String jfJavaTypeFullName = jfJavaType.getFullyQualifiedName();

            //属性是否是泛型,例如 User<T> 里有个属性 T info, 是否是 T
            JavaType genericJavaType = genericJavaTypeMap.get(jfJavaTypeFullName);
            if (genericJavaType != null) {//如果是泛型,把T替换成实际的类型,例如 User<T> 里的属性 T info, 使用的是User<String>, T就是String
                jfJavaType = genericJavaType;
                jfJavaTypeFullName = jfJavaType.getFullyQualifiedName();
            }
            //是否是基础类型
            String[] javaBaseType = getJavaBaseType(jfJavaTypeFullName);

            //属性的componentsSchemasObjectPropertyMap,放入基础字段属性
            Map<String, Object> componentsSchemasObjectPropertyMap = new HashMap<>();
            //字段的name为key
            componentsSchemasObjectPropertiesMap.put(name, componentsSchemasObjectPropertyMap);

            //如果属性是Object对象,例如 User u=null,用属性递归调用
            if (javaBaseType == null || javaBaseType.length < 2) {//object对象
                componentsSchemasObjectPropertyMap.put("$ref", "#/components/schemas/" + getRefPath(jfJavaType));
                addComponentsSchemasRef(jfJavaType, componentsSchemasMap);

            } else if ("array".equalsIgnoreCase(javaBaseType[0])) {//如果是数组,例如 List<String> infos;
                //获取数组里的泛型,调用数组的添加方法.
                List<JavaType> actualTypeArguments = ((JavaParameterizedType) jfJavaType).getActualTypeArguments();
                wrapArrayGeneric(componentsSchemasObjectPropertyMap, actualTypeArguments, genericJavaTypeMap, componentsSchemasMap);

            } else {//如果是普通类型,例如 String name="abc"
                //设置 type和format
                componentsSchemasObjectPropertyMap.put("type", javaBaseType[0]);
                componentsSchemasObjectPropertyMap.put("format", javaBaseType[1]);
                //字段的注释
                String getComment = javaField.getComment();
                if (StringUtils.isNotBlank(getComment)) {
                    componentsSchemasObjectPropertyMap.put("description", getComment);
                }

            }

        }

        //如果有必填字段
        if (CollectionUtils.isNotEmpty(componentsSchemasObjectRequiredList)) {
            componentsSchemasObjectMap.put("required", componentsSchemasObjectRequiredList);
        }

    }

    /**
     * 包装 泛型数组,例如 List<User> 取到 User 这个类型,在schemaRefObjectMap中items展开,User这个类型放到componentsSchemasMap中
     *
     * @param schemaRefMap         openapi中,记录参数的对象
     * @param actualTypeArguments  数组中的泛型,例如 List<User> 中的 User
     * @param genericJavaTypeMap   代码中声明的泛型,这个是递归使用的,比如List<User<Data>>,User<T> User声明的是 T 为key,Data为value,User中有个属性 List<T> infos;
     * @param componentsSchemasMap 声明的类型
     */

    private static void wrapArrayGeneric(Map<String, Object> schemaRefMap, List<JavaType> actualTypeArguments, Map<String, JavaType> genericJavaTypeMap, Map<String, Object> componentsSchemasMap) {
        //添加数组类型
        schemaRefMap.put("type", "array");
        schemaRefMap.put("format", "array");

        //添加items层级
        Map<String, Object> itemMap = new HashMap<>();
        schemaRefMap.put("items", itemMap);

        if (CollectionUtils.isEmpty(actualTypeArguments)) {//没有泛型,只是一个单纯的List
            itemMap.put("type", "object");//item下的type是object
        } else {//如果有泛型,判断是否是基础类型的泛型
            //因为是List,所以可以直接取值第一个泛型类型
            JavaType arrayJavaType = actualTypeArguments.get(0);
            //获取泛型的类型,例如 java.lang.String
            String parameterTypeName = arrayJavaType.getFullyQualifiedName();
            //属性是否是类型上声明的泛型,例如 User<T> 对象里有个属性  List<T> infos
            JavaType genericJavaType = genericJavaTypeMap.get(parameterTypeName);
            if (genericJavaType != null) {//如果是声明的泛型,就要把处理的类型从T改成实际使用的Data
                arrayJavaType = genericJavaType;
                parameterTypeName = arrayJavaType.getFullyQualifiedName();
            }
            //是否是基础类型
            String[] javaBaseType = getJavaBaseType(parameterTypeName);
            if (javaBaseType == null || javaBaseType.length < 2) {//object对象,例如 List<User> 中的User
                itemMap.put("$ref", "#/components/schemas/" + getRefPath(arrayJavaType));
                //增加类型定义
                addComponentsSchemasRef(arrayJavaType, componentsSchemasMap);

            } else if ("array".equalsIgnoreCase(javaBaseType[0])) {//如果是数组,例如 List<List<User>> 泛型类还是数组,递归调用本方法.
                //获取泛型
                List<JavaType> arrayJavaTypeArguments = ((JavaParameterizedType) arrayJavaType).getActualTypeArguments();
                wrapArrayGeneric(itemMap, arrayJavaTypeArguments, genericJavaTypeMap, componentsSchemasMap);

            } else {//基础类型,例如 List<String>
                itemMap.put("type", javaBaseType[0]);
                itemMap.put("format", javaBaseType[1]);
            }
        }
    }


    /**
     * 获取ref路径
     *
     * @param javaType java类型
     * @return 合法的ref路径
     */
    private static String getRefPath(JavaType javaType) {
        String refPath = javaType.getGenericFullyQualifiedName();
        refPath = refPath.replaceAll("<", "_");
        refPath = refPath.replaceAll(">", "_");
        return refPath;
    }


    /**
     * 获取 JavaClass的字段
     *
     * @param jc   JavaClass
     * @param list 字段列表
     */
    private static void getJavaClassFields(JavaClass jc, List<JavaField> list) {
        List<JavaField> fields = jc.getFields();
        for (JavaField jf : fields) {
            boolean extis = false;
            for (int i = 0; i < list.size(); i++) {
                JavaField j = list.get(i);
                if (j.getName().equals(jf.getName())) {//如果已经存在
                    extis = true;
                    break;
                }

            }

            if (!extis) {
                list.add(jf);
            }

        }

        JavaClass superJavaClass = jc.getSuperJavaClass();
        if (superJavaClass == null) {
            return;
        }
        String fullyQualifiedName = superJavaClass.getFullyQualifiedName();
        //java根类
        if (fullyQualifiedName.startsWith("java.") || fullyQualifiedName.startsWith("javax.")) {
            return;
        }
        //递归调用属性
        getJavaClassFields(superJavaClass, list);

    }


    /**
     * 是否是忽略的字段
     *
     * @param jf java字段
     * @return 是否是忽略的字段
     */
    private static boolean isIgnoreJavaField(JavaField jf) {

        return isDocletTagJavaField(jf, ignoreDocletTag);
    }

    /**
     * 是否是必填的字段
     *
     * @param jf java字段
     * @return 是否是忽略的字段
     */
    private static boolean isRequiredJavaField(JavaField jf) {
        return isDocletTagJavaField(jf, requiredDocletTag);
    }

    /**
     * 是否是忽略的字段
     *
     * @param jf        java字段
     * @param docletTag 注释中的注解名称,例如@ignore,@required
     * @return 是否是忽略的字段
     */
    private static boolean isDocletTagJavaField(JavaField jf, String docletTag) {
        //是否是忽略的字段
        List<DocletTag> jfTagsByName = jf.getTagsByName(docletTag);
        if (CollectionUtils.isNotEmpty(jfTagsByName)) {
            return true;
        }

        return false;
    }


}
